package com.wh1200.mybatis.xmlless.config

import com.baomidou.mybatisplus.core.metadata.TableInfoHelper
import com.wh1200.mybatis.xmlless.ElementInfoBuilder
import com.wh1200.mybatis.xmlless.constant.Strings
import com.wh1200.mybatis.xmlless.enums.XmllessDbType
import com.wh1200.mybatis.xmlless.handler.JsonTypeHandler
import com.wh1200.mybatis.xmlless.model.Properties
import com.wh1200.mybatis.xmlless.model.dto.DefaultParameterWrapper
import com.wh1200.mybatis.xmlless.replaceSpace
import com.wh1200.mybatis.xmlless.resolver.ColumnsResolver
import com.wh1200.mybatis.xmlless.resolver.CriteriaResolver
import com.wh1200.mybatis.xmlless.resolver.MappingResolver
import com.wh1200.mybatis.xmlless.resolver.TypeResolver
import com.wh1200.xmlless.test.dao.StudentDAO
import com.wh1200.xmlless.test.dao.StudentJavaDAO
import com.wh1200.xmlless.test.model.Score
import com.wh1200.xmlless.test.model.Student
import com.wh1200.xmlless.test.model.StudentVO
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import kotlin.reflect.jvm.javaMethod
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertNotNull


/**
 *
 * Created by 吴昊 on 2018-12-09.
 *
 * @author 吴昊
 * @since 0.0.1
 */
class StudentDAOResolverTest : BaseResolverTest(
    StudentDAO::class.java
) {

  companion object {

    private val log: Logger = LoggerFactory.getLogger(StudentDAOResolverTest::class.java)
  }

  @Test
  fun mappingResolve() {
    val allMappings = MappingResolver.getAllMappings()
    val scoreMapping = allMappings.first {
      it.modelClassName == Score::class.java.name
    }
    scoreMapping.mappings.forEach {
      if (it.property == "studentId") {
        assertEquals("student_id", it.column)
      }
    }
  }

  @Test
  @DisplayName("查询条件匹配json内的字段")
  fun queryInJson() {
    val q = createQueryForMethod(StudentDAO::findByScoreGt.javaMethod!!)
    val sql = q.sql
    assertNotNull(sql)
    if (dbType == XmllessDbType.POSTGRE_SQL) {
      assertContains(sql, "t_student.detail-&gt;&gt;'score' &gt;")
    } else {
      assertContains(sql, "t_student.detail-&gt;'\$.score' &gt;")
    }
    TableInfoHelper.getTableInfo(Student::class.java).fieldList.forEach {
      if (it.property == "nickNames") {
        assert(it.typeHandler == JsonTypeHandler::class.java)
      }
    }
  }

  @Test
  fun resolveDeleteById() {
    val sql = createQueryForMethod(StudentDAO::deleteById.javaMethod!!).sql
    assertNotNull(sql)
    assertContains(
        sql, "SET\n" +
        "  del_flag = true"
    )
    assertContains(sql, "id = #{id}")
  }

  @Test
  @DisplayName("对象数组是否包含指定对象")
  fun containsObject() {
    val sql = createQueryForMethod(StudentDAO::findByDataContains.javaMethod!!).sql
    println(sql)
  }

  @Test
  @DisplayName("测试exists携带逻辑删除条件")
  fun resolveExistsById() {
    val sql = createQueryForMethod(StudentDAO::existsById.javaMethod!!).sql
    assertNotNull(sql)
    assertContains(sql, "del_flag = false")
  }

  @Test
  fun resolveDeleteByIds() {
    val sql = createQueryForMethod(StudentDAO::deleteByIds.javaMethod!!).sql
    assertNotNull(sql)
    println(sql)
    assertContains(
        sql.replaceSpace(),
        """<foreach collection="ids" item="item" separator=", " open="(" close=")"> #{item} </foreach>"""
    )
  }

  @Test
  fun resolveColumns() {
    val c2 = TypeResolver.resolveReturnMappingType(StudentDAO::findVOPage.javaMethod!!, modelClass)
    assertEquals(StudentVO::class.java, c2)
    val methodInfo = createMethodInfo(StudentDAO::findVO.javaMethod!!)
    val cols = ColumnsResolver.resolveSelectColumns(Properties(), methodInfo)
    assertEquals(6, cols.size)
  }

  @Test
  fun resolveFindBySubjectId() {
    val query = createQueryForMethod(StudentDAO::findByCreateTimeEqDate.javaMethod!!)
    query.exception?.printStackTrace()
    val sql = query.sql
    assertNotNull(sql)
    log.info(sql)
    if (dbType == XmllessDbType.POSTGRE_SQL) {
      assertContains(sql, "TO_CHAR(t_student.create_time, 'YYYY-MM-DD') = TO_CHAR(#{createTime}, 'YYYY-MM-DD')")
    } else {
      assertContains(sql, "DATE_FORMAT(t_student.create_time, '%Y-%m-%d') = DATE_FORMAT(#{createTime}, '%Y-%m-%d')")
    }
  }

  @Test
  fun resolveFindByNameLikeAndAgeAndCreateTimeBetweenStartAndEnd() {
    val sql = createQueryForMethod(StudentDAO::findByNameLikeAndAgeAndCreateTimeBetweenStartAndEnd.javaMethod!!).sql
    assertNotNull(sql)
    assertContains(sql, "t_student.name LIKE CONCAT('%', #{name},'%') AND")
  }

  @Test
  @DisplayName("关联表字段条件查询")
  fun findByUserNameLike() {
    val query = createQueryForMethod(StudentDAO::findByUserNameLike.javaMethod!!)
    assertNotNull(query.query)
    val sql = query.sql
    assertNotNull(sql)
    assert(sql.contains("u.name"))
    assertContains(sql, "del_flag = false")
    assertEquals(sql.indexOf("del_flag = 0"), sql.lastIndexOf("del_flag = 0"))
  }

  @Test
  @DisplayName("测试查询逻辑删除为未删除状态的数据")
  fun find() {
    val query = createQueryForMethod(StudentDAO::find.javaMethod!!)
    query.exception?.printStackTrace()
    assertNotNull(query.query)
    val sql = query.sql
    assertNotNull(sql)
    assertContains(sql, """<if test="form.start != null and form.end != null">""")
    assert(sql.contains("u.name"))
    assert(!sql.contains("\"name"))
    assertContains(sql, "del_flag = false")
    assertEquals(sql.indexOf("del_flag = 0"), sql.lastIndexOf("del_flag = 0"))
  }

  @Test
  fun resolveInsert() {
    val sql = createQueryForMethod(StudentDAO::save.javaMethod!!).sql
    assertNotNull(sql)
  }

  @Test
  fun find3() {
    val q = createQueryForMethod(StudentDAO::find3.javaMethod!!)
    q.exception?.printStackTrace()
    val sql = q.sql
    assertNotNull(sql)
    assertContains(sql, "t_student.birthday IS NOT NULL")
  }

  @Test
  @DisplayName("测试not")
  fun testNot() {
    val q = createQueryForMethod(StudentDAO::findByIdNot.javaMethod!!)
    q.exception?.printStackTrace()
    val sql = q.sql
    assertNotNull(sql)
    assertContains(sql, "t_student.id != #{id}")
  }

  @Test
  @DisplayName("测试boolean字段true")
  fun testTrue() {
    val q = createQueryForMethod(StudentDAO::findByGraduatedTrue.javaMethod!!)
    q.exception?.printStackTrace()
    val sql = q.sql
    assertNotNull(sql)
    assertContains(sql, "t_student.graduated = TRUE")
  }

  @Test
  @DisplayName("测试条件中使用函数")
  fun find4() {
    val query = createQueryForMethod(StudentDAO::find4.javaMethod!!)
    val sql = query.sql
    println(sql)
    assertNotNull(sql)
    assertContains(
        sql, """<when test="form.type == 2 and form.currentUserId != null">"""
    )
    assertContains(sql, "<if test=\"form.name != null and form.name.length() &gt; 0\">")
    assertContains(sql, "LIMIT 1")
    assertContains(sql, """t_student.update_user_id = #{form.currentUserId} AND""")
    assertContains(
        sql, "date_format(t_student.create_time, '%Y-%m-%d') &lt;= date_format(#{form.endDate}, " +
        "'%Y-%m-%d') AND"
    )
  }

  @Test
  @DisplayName("max函数")
  fun findMaxAge() {
    val method = StudentDAO::selectMaxAge.javaMethod!!
    val q = createQueryForMethod(method)
    assertNotNull(q.sql)
  }

  @Test
  @DisplayName("测试choose注解")
  fun testChoose() {
    val method = StudentDAO::find6.javaMethod!!
    val methodInfo = createMethodInfo(method)
    val detailInfo = ElementInfoBuilder.build(DefaultParameterWrapper(method.parameters.first()))
    val list = CriteriaResolver.getTestCriteriaInfo(detailInfo, methodInfo)
    assertEquals(2, list.size)
    val query = createQueryForMethod(method)
    query.exception?.printStackTrace()
    val sql = query.sql
    assertNotNull(sql)
    assertContains(sql, "<when test=\"type = 1\">")
    assertContains(sql, "<when test=\"type = 2\">")
  }

  @Test
  @DisplayName("在方法参数上添加@TestCriteria注解")
  fun find5() {
    val method = StudentDAO::find5.javaMethod!!
    val methodInfo = createMethodInfo(method)
    val detailInfo = ElementInfoBuilder.build(DefaultParameterWrapper(method.parameters.first()))
    val list = CriteriaResolver.getTestCriteriaInfo(detailInfo, methodInfo)
    assertEquals(2, list.size)
    val query = createQueryForMethod(method)
    query.exception?.printStackTrace()
    val sql = query.sql
    assertNotNull(sql)
    assertContains(sql, "<when test=\"type = 1\">")
    assertContains(sql, "<when test=\"type = 2\">")
  }

  @Test
  fun findByCreateTimeBetweenStartTimeAndEndTime() {
    val sql = createQueryForMethod(StudentDAO::findByCreateTimeBetweenStartTimeAndEndTime.javaMethod!!).sql
    assertNotNull(sql)
  }

  @Test
  @DisplayName("测试条件之间用Or连接")
  fun findByNameOrAge() {
    val query = createQueryForMethod(StudentDAO::findByNameOrAge.javaMethod!!)
    query.query!!.criterion.forEach {
      println(it.getTests(null))
    }
    val sql = query.sql
    assertNotNull(sql)
  }

  @Test
  @DisplayName("查询字段使用sql函数")
  fun statistics() {
    val query = createQueryForMethod(StudentDAO::statistics.javaMethod!!)
    query.exception?.printStackTrace()
    assertNotNull(query.query)
    val sql = query.sql
    assertNotNull(sql)
    assert(sql.contains("AS avgAge"))
    assert(sql.contains("AS sumAge"))
    assert(sql.contains("sum(t_student.age)"))
    assert(sql.contains("avg(t_student.age)"))
  }

  @Test
  @DisplayName("查询字段使用sql函数 分页")
  fun statistics2() {
    val query = createQueryForMethod(StudentDAO::statistics2.javaMethod!!)
    query.exception?.printStackTrace()
    assertNotNull(query.query)
    val sql = query.sql
    assertNotNull(sql)
    assertContains(sql, "AS avgAge")
    assertContains(sql, "AS sumAge")
    assertContains(sql, "sum(t_student.age)")
    assert(sql.contains("avg(t_student.age)"))
  }

  @Test
  @DisplayName("返回map")
  fun statistics3() {
    val method = StudentDAO::statistics3.javaMethod!!
    val methodInfo = createMethodInfo(method)
    assertContains(methodInfo.propertiesMapping, "avgAge")
    assertContains(methodInfo.propertiesMapping, "age")
    assertContains(methodInfo.propertiesMapping, "sumAge")
    assertContains(methodInfo.propertiesMapping, "count")
    val query = createQueryForMethod(method)
    query.exception?.printStackTrace()
    assertNotNull(query.query)
    val sql = query.sql
    assertNotNull(sql)
    assert(sql.contains("AS avg_age"))
    assert(sql.contains("AS sum_age"))
    assert(sql.contains("sum(t_student.age)"))
    assert(sql.contains("avg(t_student.age)"))
  }

  @Test
  @DisplayName("统计指定字段")
  fun countProperty() {
    val method = StudentDAO::countNameById.javaMethod!!
    val query = createQueryForMethod(method)
    query.exception?.printStackTrace()
    val sql = query.sql
    assertNotNull(sql)
    assertContains(sql, "COUNT(t_student.name)")
  }

  @Test
  @DisplayName("返回字段使用函数")
  fun selectColumnUseFunction() {
    val method = StudentDAO::selectAgeSum.javaMethod!!
    val query = createQueryForMethod(method)
    query.exception?.printStackTrace()
    val sql = query.sql
    assertNotNull(sql)
    assertContains(sql, "sum(age)")
  }

  @Test
  fun test() {
    val q = createQueryForMethod(StudentDAO::findByNameAndAge.javaMethod!!)
    println(q.sql)
  }

  @Test
  @DisplayName("返回vo类，vo类中包含关联字段")
  fun findVO() {
    val c = TypeResolver.resolveReturnMappingType(StudentDAO::findVO.javaMethod!!, modelClass)
    assertEquals(StudentVO::class.java, c)
    val query = createQueryForMethod(StudentDAO::findVO.javaMethod!!)
    query.exception?.printStackTrace()
    assertNotNull(query.query)
    val sql = query.sql
    assertNotNull(sql)
    assertContains(sql, "createUser.name")
    assertContains(sql.replaceSpace(), "LEFT JOIN t_user createUser")
    assertContains(sql.replaceSpace(), "LEFT JOIN xx ")
    assert(sql.contains("school.wz LIKE CONCAT('%', #{keywords}, '%')"))
    assert(!sql.contains("create_time"))
    assertContains(sql, "del_flag = false")
    assertEquals(sql.indexOf("del_flag = 0"), sql.lastIndexOf("del_flag = 0"))
  }

  @Test
  @DisplayName("between省略参数名称，按方法参数顺序解析")
  fun findByAgeBetween() {
    val sql = createQueryForMethod(StudentDAO::findByAgeBetween.javaMethod!!).sql
    assertNotNull(sql)
    assertContains(sql, "t_student.age BETWEEN #{min} AND #{max} AND")
  }

  @Test
  @DisplayName("between指定参数名称")
  fun findByAgeBetweenMinAndMax() {
    val sql = createQueryForMethod(StudentDAO::findByAgeBetweenMinAndMax.javaMethod!!).sql
    assertNotNull(sql)
    assertContains(sql, "t_student.age BETWEEN #{min} AND #{max} AND")
  }

  @Test
  @DisplayName("返回对象不是持久化对象且包含json字段")
  fun findSimpleVO() {
    val sql = createQueryForMethod(StudentDAO::findSimpleList.javaMethod!!).sql
    assertNotNull(sql)
    assertContains(sql, "t_student.detail AS detail")
  }

  @Test
  @DisplayName("测试Like匹配前缀")
  fun resolveFindByPhoneNumberLikHeLeft() {
    val sql = createQueryForMethod(StudentDAO::findByPhoneNumberLikeLeft.javaMethod!!).sql
    assertNotNull(sql)
    assert(!sql.contains("</if>"))
    assertContains(sql, "t_student.phone_number LIKE CONCAT('%', #{phoneNumber}) AND")
  }

  @Test
  fun findContainsAny() {
    val sql = createQueryForMethod(StudentDAO::findContainsAny.javaMethod!!).sql
    assertNotNull(sql)
    assertContains(sql, Strings.TO_JSON_CALL)
    if (dbType == XmllessDbType.MYSQL) {
      assertContains(sql, "JSON_CONTAINS(t_student.detail->'\$.roles'")
      assertContains(sql, "<foreach collection=\"roles\" item=\"item\" separator=\" or \">")
    } else if (dbType == XmllessDbType.POSTGRE_SQL) {
      assertContains(sql, "(t_student.detail->>'roles')::jsonb @> ANY")
    }
  }

  @Test
  fun findByJsonField() {
    val sql = createQueryForMethod(StudentDAO::findByJsonField.javaMethod!!).sql
    assertNotNull(sql)
    if (dbType == XmllessDbType.POSTGRE_SQL) {
      assertContains(sql, "(t_student.detail")
      assertContains(sql, "::integer")
    }
  }

  @Test
  fun find999() {
    val sql = createQueryForMethod(StudentDAO::findContainsInt.javaMethod!!).sql
    assertNotNull(sql)
  }

  @Test
  fun findContainsString() {
    val sql = createQueryForMethod(StudentDAO::findContainsString.javaMethod!!).sql
    assertNotNull(sql)
  }

  @Test
  fun find888() {
    val sql = createQueryForMethod(StudentDAO::find888.javaMethod!!).sql
    assertNotNull(sql)
  }

  @Test
  fun findInObjectArray() {
    val sql = createQueryForMethod(StudentDAO::findInObjectArray.javaMethod!!).sql
    assertNotNull(sql)
    if (dbType == XmllessDbType.POSTGRE_SQL) {
      assertContains(sql, "t_student.data->>'\$[*].height'")
    } else {
      assertContains(sql, "JSON_CONTAINS(t_student.data->'\$[*].height', '\$', 2)")
    }
  }

  @Test
  fun appendData() {
    val q = createQueryForMethod(StudentDAO::appendData.javaMethod!!)
    val sql = q.sql
    assertNotNull(sql)
    assertContains(sql, "@com.wh1200.mybatis.xmlless.model.QueryCriteria@toJson(data)")
  }

  @Test
  @DisplayName("测试update")
  fun update() {
    val sql = createQueryForMethod(StudentDAO::update.javaMethod!!).sql
    assertNotNull(sql)
  }

  @Test
  fun resolveSaveOrUpdate() {
    val method = StudentDAO::saveOrUpdate.javaMethod!!
    val query = createQueryForMethod(method)
    val sql = query.sql
    assertNotNull(sql)
  }

  @Test
  @DisplayName("测试指定更新部分字段, 使用持久化对象作为参数")
  fun updatePartlyUseModelClassAsParam() {
    val sql = createQueryForMethod(StudentDAO::updatePartly.javaMethod!!).sql
    assertNotNull(sql)
    assertContains(
        sql.replaceSpace(), """<if test="name != null"> name = #{name}, </if>""".trimMargin()
    )
    assertContains(sql, "phone_number = '222',")
    assertContains(sql, "age = age + 1")
    assert(sql.contains("<if test=\"grade != null\">"))
    assert(!sql.contains("age = #{age},"))
  }

  @Test
  @DisplayName("测试指定更新部分字段,使用非持久化对象作为参数")
  fun updatePartlyUseVOAsParam() {
    val sql = createQueryForMethod(StudentDAO::updatePartly2.javaMethod!!).sql
    assertNotNull(sql)
    assertContains(
        sql.replaceSpace(), """<if test="name != null"> name = #{name}, </if>""".trimMargin()
    )
    assert(sql.contains("<if test=\"grade != null\">"))
    assert(sql.contains("age = 22,"))
  }

  @Test
  @DisplayName("测试更新指定字段为特定值")
  fun updateBirthday() {
    val sql = createQueryForMethod(StudentDAO::updateBirthday.javaMethod!!).sql
    assertNotNull(sql)
  }

  @Test
  @DisplayName("测试批量插入")
  fun saveAll() {
    val query = createQueryForMethod(StudentJavaDAO::saveAll.javaMethod!!)
    val sql = query.sql
    assertNotNull(sql)
    assertContains(sql, """<foreach collection="list" item="item" separator=",">""")
  }

  @Test
  @DisplayName("测试findById 包含逻辑删除")
  fun resolveFindById() {
    val method = StudentDAO::findById.javaMethod!!
    val sql = createQueryForMethod(method).sql
    assertNotNull(sql)
    assertContains(sql, "t_student.del_flag = false")
  }

  @Test
  @DisplayName("测试返回值映射解析")
  fun resolveResultMap() {
    val method = StudentDAO::findById.javaMethod!!
    createQueryForMethod(method)
    val resultMaps = builderAssistant.configuration.resultMaps
    val ids = resultMaps.map { it.id }
    assert(ids.contains(buildResultMapId(StudentDAO::findById.javaMethod!!)))
    assert(ids.contains("${buildResultMapId(StudentDAO::findById.javaMethod!!)}_scores"))
    assert(ids.contains("${buildResultMapId(StudentDAO::findById.javaMethod!!)}_scores_subject"))
    val scoreMap = resultMaps.first { it.id == "${buildResultMapId(StudentDAO::findById.javaMethod!!)}_scores" }
    assert(scoreMap.autoMapping == true)
    assert(scoreMap.hasNestedResultMaps())
    val resultMappings = scoreMap.propertyResultMappings
    assert(resultMappings.any { it.column == "subjectId" })
    assert(resultMappings.any { it.property == "subject" })
  }

  @Test
  @DisplayName("测试=TRUE的解析")
  fun resolveSpecValue() {
    val query = createQueryForMethod(StudentDAO::findByGraduatedEqTrue.javaMethod!!)
    assertContains(query.toString(), "graduated = TRUE")
    val query2 = createQueryForMethod(StudentDAO::findByGraduatedEqTrue2.javaMethod!!)
    assertContains(query2.toString(), "graduated = TRUE")
  }

}
